<?php

require_once 'smarty/Smarty.class.php';

/**
 * Ответ приложения в формате HTML. Использует Smarty.
 *
 */
class Kernel_Response_Html extends Kernel_Response_Abstract 
{

    /**
     * Наименование переменной обрамляющего шаблона, в которую будет передан
     * результат работы модуля.
     *
     */
    const CONTENTS_VAR = 'contents';

    /**
     * Наименование обрамляющего шаблона по умолчанию.
     *
     */
    const DEFAULT_FRAME_TEMPLATE = 'default';

    /**
     * Шаблон преобразования наименования обрамляющего шаблона в путь.
     *
     */
    const FRAME_TEMPLATE_PATH = '_frame/%s.tpl';
    
    /**
     * Шаблон преобразования наименования внутреннего шаблона в путь.
     *
     */
    const INNER_TEMPLATE_PATH = '_inner/%s.tpl';

    /**
     * Путь к файлу шаблона ответа
     *
     * @var string
     */
    protected $_template;

    /**
     * Путь к файлу обрамляющего шаблона
     *
     * @var string
     */
    protected $_frame_template;

    /**
     * Шаблонный движок
     *
     * @var Smarty
     */
    private $_engine;

    /**
     * Название страницы.
     *
     * @var string
     */
    private $_title;

    /**
     * Метаданные страницы.
     *
     * @var string
     */
    private $_meta = array();

    /**
     * Дополнительные скрипты, которые нужно загрузить для формирования страницы.
     *
     * @var array
     */
    private $_scripts = array();

    /**
     * Дополнительные таблицы стилей, которые нужно загрузить для формирования
     * страницы.
     *
     * @var array
     */
    private $_styles = array();

    /**
     * Основной путь для формирования относительных ссылок.
     *
     * @var string
     */
    private $_base;
    
    /**
     * Конструктор
     *
     */
    public function __construct() 
    {
        $this->header('Content-type', 'text/html');
        $this->_engine = $this->createEngine();
    }

    /**
     * Создает движок для сборки ответа. :TODO: morozov 10092007: вынести
     * в отдельное место.
     *
     * @return  Smarty
     */
    public static function createEngine() 
    {
        $engine = new Smarty;

        // настраиваем пути к директориям
        $engine->template_dir = array(SITE_ROOT . '/_templates', KERNEL_PATH . '/templates');
        $engine->compile_dir  = SITE_ROOT . '/_smarty/compiled';
        $engine->config_dir   = SITE_ROOT . '/_smarty/config';
        $engine->cache_dir    = SITE_ROOT . '/_smarty/cache';

        // здесь будут лежать плагины смарти ядра, общие для всех проектов
        $engine->plugins_dir[] = KERNEL_PATH . '/plugins';

        // а здесь - плагины проекта. пускай будет на всякий случай
        $engine->plugins_dir[] = SITE_ROOT . '/_smarty/plugins';

        // где-то начиная с версии 2.6.12 если значение error_reporting не
        // указано, используется error_reporting() & ~E_NOTICE. указываем явно.
        $engine->error_reporting = error_reporting();
        return $engine;
    }

    /**
     * Устанавливает значения переменных.
     *
     * @param   mixed   $name
     * @param   mixed   $value
     * @return  Kernel_Response_Html
     */
    public function assign($name, $value = null) 
    {
        $this->_engine->assign($name, $value);
        return $this;
    }

    /**
     * Добавляет скрипт.
     *
     * @param   string  $src
     * @return  Kernel_Response_Html
     */
    public function script($src) 
    {
        $this->_scripts[] = $src;
        return $this;
    }

    /**
     * Добавляет стиль.
     *
     * @param   string  $src
     * @return  Kernel_Response_Html
     */
    public function style($src) 
    {
        $this->_styles[] = $src;
        return $this;
    }

    /**
     * Возвращает скрипты страницы.
     *
     * @return  array
     */
    public function scripts() 
    {
        return array_unique($this->_scripts);
    }

    /**
     * Возвращает стили страницы.
     *
     * @return  array
     */
    public function styles() 
    {
        return array_unique($this->_styles);
    }

    /**
     * Устанавливает название страницы.
     *
     * @param   string  $value
     * @return  Kernel_Response_Html
     */
    public function setTitle($value) 
    {
        $this->_title = $value;
        return $this;
    }

    /**
     * Устанавливает метаданные страницы.
     *
     * @param   array   $value
     * @return  Kernel_Response_Html
     */
    public function setMeta($meta) 
    {

        // заменяем значения одноименных заголовков на новые
        $this->_meta = array_merge($this->_meta,

            // отфильтровываем пустые значения
            array_filter($meta, create_function('$value', 'return strlen($value) > 0;')));
        return $this;
    }

    /**
     * Устанавливает путь к файлу шаблона ответа.
     *
     * @param   string  $template
     * @return  Kernel_Response_Html
     */
    public function setTemplate($template) 
    {
        $this->_template = $template;
        return $this;
    }

    /**
     * Устанавливает путь к обрамляющему шаблону.
     *
     * @param   string  $name
     * @return  Kernel_Response_Html
     */
    public function setFrameTemplate($name) 
    {
        $this->_frame_template = $name;
        return $this;
    }

    /**
     * Устанавливает основной путь, если указан, и возвращает предыдущее значение.
     *
     * @param   string  $base
     * @return  string
     */
    public function base($base = null) 
    {
        $_base = $this->_base;
        if (func_num_args() > 0) {
            $this->_base = $base;
        }
        return $_base;
    }

    /**
     * Выводит в ответ ошибку, описанную указанным исключением.
     *
     * @param   Kernel_Filter_Exception $e
     * @return  Kernel_Response_Html
     */
    public function error($e) 
    {
        return parent::error($e)->assign(array(
            'status' => $e->getCode(),
            'status_text' => $e->getStatusText(),
            'error' => $e->getMessage(),
            'params' => $e->getParams(),
            'user_message' => Kernel_Config::get('frontend', 'error_' . $e->getCode(), null), 
        ))
            // подменяем шаблоны
            ->setFrameTemplate(false)->setTemplate($this->_getErrorTemplate());
    }

    /**
     * Отображает указанное сообщение.
     *
     * @param   Kernel_Response_Message $message
     */
    protected function _message(Kernel_Response_Message $message) 
    {
        // сохраняем сообщение в сессии
        $message->store();
    }

    /**
     * Возвращает содержимое ответа, сгенерированное по шаблону.
     *
     * @return  string
     */
    protected function _template() 
    {
        // получаем содержимое, сгенерированное модулем
        $contents = $this->_wrap($this->_fetch($this->_template));
        if (Kernel_Application::isDebugMode()) {
            $contents .= $this->_debug();
        }
        return $contents;
    }

    /**
     * Оборачивает содержимое страницы в обрамляющий шаблон.
     *
     * @param   string  $contents
     * @return  string
     */
    protected function _wrap($contents) 
    {

        // если обрамляющий шаблон НЕ БЫЛ УКАЗАН
        if (null === $this->_frame_template) {
            $this->_frame_template = self::DEFAULT_FRAME_TEMPLATE;
        }

        // если НЕ ЗАДАН обрамляющий шаблон
        if (false === $this->_frame_template) {
            return $contents;
        }

        $this->_assignModuleContents($contents);

        // определяем, именованный ли это шаблон или путь к файлу
        if (false === strpos($this->_frame_template, '.')) {
            $this->_frame_template = sprintf(self::FRAME_TEMPLATE_PATH, $this->_frame_template);
        }

        // оборачиваем содержимое в обрамляющий шаблон
        return $this->_fetch($this->_frame_template);
    }

    /**
     * Передает содержимое модуля в обрамляющий шаблон шаблон.
     *
     */
    protected function _assignModuleContents($contents) 
    {
        $this->assign(array(
            '_title' => $this->_title,
            '_meta'  => $this->_meta,
            '_scripts' => array_unique($this->_scripts),
            '_styles' => array_unique($this->_styles),
            self::CONTENTS_VAR => $contents,
        ));
    }

    /**
     * Оборачивает данные ответа в указанный шаблон.
     *
     * @param   string  $template
     * @return  string
     */
    private function _fetch($template) 
    {
        // do not need template (e.g. all the data assigned into frame template)
        if (false === $template) {
            return '';
        }
        
        // define whether we have named template or path to the template
        if (false === strpos($this->_template, '.')) {
            $this->_template = sprintf(self::INNER_TEMPLATE_PATH, $this->_template);
        }
        
        // передаем в шаблон дополнительные данные, сгенерированные в ходе
        // работы приложения
        $this->_engine->assign(array(
            'BASE_PATH' => $this->_base,
               '_debug' => Kernel_Application::isDebugMode(),
                '_lang' => __lang(), 
        ));
        
        return $this->_engine->fetch($template);
    }

    /**
     * Возвращает отладочную информацию.
     *
     * @return  string
     */
    private function _debug() 
    {
        return implode("\n", array (
            '',
            '<!--',
            '',
            '    ' . database::count() . ' queries executed', 
            '    Frame template:  ' . $this->_quotize($this->_getRealPath($this->_frame_template)),
            '    Module template: ' . $this->_quotize($this->_getRealPath($this->_template)),
            '',
            '//-->',
        ));
    }

    /**
     * Возвращает абсолютный путь к указанному файлу шаблона.
     *
     * @param   string  $template
     * @return  string
     */
    private function _getRealPath($template) 
    {
        // предусматриваем случай, когда задано несколько значений template_dir
        foreach ((array)$this->_engine->template_dir as $dir) {
            if (is_file($path = $dir . '/' . $template)) {
                return realpath($path);
            }
        }
        return null;
    }

    /**
     * Оборачивает ненулевой путь в кавычки.
     *
     * @param   string  $path
     * @return  string
     */
    private function _quotize($path) 
    {
        return null !== $path ? '"' . $path . '"' : 'none';
    }

    /**
     * Возвращает путь к шаблону страницы об ошибке.
     *
     * @return  string
     */
    private function _getErrorTemplate() 
    {
        // locate error template file depending on environment
        return require dirname($_SERVER['SCRIPT_FILENAME']) . '/_includes/template.php';
    }
    
}